/*
* Copyright (c) 2006-2007 Erin Catto http://www.box2d.org
*
* This software is provided 'as-is', without any express or implied
* warranty.  In no event will the authors be held liable for any damages
* arising from the use of this software.
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.


********************************************************************************

SandBox2D - Tools and Middleware 2011/2012 Assignment 1  - Sam Latif MA002HA

A heavily modified testbed, with added runtime shape creation/editing, FMOD sounds and XML meta data save/load functionality
 

*/

#include "../Headers/Render.h"
#include "../Headers/SandBox2D.h"
#include "glui/glui.h"

#include <cstdio>
//using namespace std;

namespace
{

	int shapeSelection = 0;

	SandBox2D* sandBoxApp;
	Settings settings;
	GLUI *glui;
	
	int32 mainWindow;
	float32 viewZoom = 1.0f;
	int tx, ty, tw, th;
	b2Vec2 lastPoint;

}

static void Resize(int32 w, int32 h)
{
	settings.screenWidth = w;
	settings.screenHeight = h;

	GLUI_Master.get_viewport_area(&tx, &ty, &tw, &th);
	
	glViewport(tx, ty, tw, th);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	float32 ratio = float32(tw) / float32(th);

	b2Vec2 extents(ratio * 25.0f, 25.0f);
	extents *= viewZoom;

	b2Vec2 lower = settings.viewCenter - extents;
	b2Vec2 upper = settings.viewCenter + extents;

	// L/R/B/T
	gluOrtho2D(lower.x, upper.x, lower.y, upper.y);
}

static b2Vec2 ConvertScreenToWorld(int32 x, int32 y)
{
	float32 u = x / float32(tw);
	float32 v = (th - y) / float32(th);

	float32 ratio = float32(tw) / float32(th);
	b2Vec2 extents(ratio * 25.0f, 25.0f);
	extents *= viewZoom;

	b2Vec2 lower = settings.viewCenter - extents;
	b2Vec2 upper = settings.viewCenter + extents;

	b2Vec2 p;
	p.x = (1.0f - u) * lower.x + u * upper.x;
	p.y = (1.0f - v) * lower.y + v * upper.y;
	return p;
}

// This is used to control the frame rate (60Hz).
static void Timer(int)
{
	glutSetWindow(mainWindow);
	glutPostRedisplay();
	glutTimerFunc(16, Timer, 0);
}

static void SimulationLoop()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	sandBoxApp->SetTextLine(30);
	b2Vec2 oldCenter = settings.viewCenter;
	settings.hz = 60;
	sandBoxApp->Step(&settings);
	if (oldCenter.x != settings.viewCenter.x || oldCenter.y != settings.viewCenter.y)
	{
		Resize(settings.screenWidth, settings.screenHeight);
	}

	sandBoxApp->RenderText(5, 15, "SandBox2D - Sam Latif MA002HA - *Press F1 to toggle instructions*");
	if (settings.showHelp)
	{
		sandBoxApp->RenderText(5, 30, "Left click to select and drag shapes, SHIFT + Left click to update shape (select shape first)");
		sandBoxApp->RenderText(5, 45, "Right click to add new shapes, SHIFT + Right click to delete shape (select shape first)");
		sandBoxApp->RenderText(5, 60, "Use arrows to move camera, mouse scroll to zoom in/out, S/L to Save and Load and P/R to Pause and Reset");
	}


	glutSwapBuffers();
}

static void Restart(int)
{
	delete sandBoxApp;
	sandBoxApp = new SandBox2D();
	Resize(settings.screenWidth, settings.screenHeight);
}

static void Keyboard(unsigned char key, int x, int y)
{
	B2_NOT_USED(x);
	B2_NOT_USED(y);

	switch (key)
	{
	case 27:
#ifndef __APPLE__
		// freeglut specific function
		glutLeaveMainLoop();
#endif
		exit(0);
		break;

	case 'r':
		delete sandBoxApp;
		sandBoxApp = new SandBox2D();
		break;

	case 'p':
		settings.pause = !settings.pause;
		break;

	case 's':
		sandBoxApp->SaveMeta();
		break;

	case 'l':
		Restart(0);
		settings.pause = true;
		sandBoxApp->LoadMeta();
		break;

			
	default:
		if (sandBoxApp)
		{
			sandBoxApp->Keyboard(key);
		}
	}
}

static void KeyboardSpecial(int key, int x, int y)
{
	B2_NOT_USED(x);
	B2_NOT_USED(y);

	switch (key)
	{
		// toggle the display of help text
	case GLUT_KEY_F1:
		settings.showHelp = !settings.showHelp;
		break;

		// Press left to pan left.
	case GLUT_KEY_LEFT:
		settings.viewCenter.x -= 10.5f;
		Resize(settings.screenWidth, settings.screenHeight);
		break;

		// Press right to pan right.
	case GLUT_KEY_RIGHT:
		settings.viewCenter.x += 10.5f;
		Resize(settings.screenWidth, settings.screenHeight);
		break;

		// Press down to pan down.
	case GLUT_KEY_DOWN:
		settings.viewCenter.y -= 10.5f;
		Resize(settings.screenWidth, settings.screenHeight);
		break;

		// Press up to pan up.
	case GLUT_KEY_UP:
		settings.viewCenter.y += 10.5f;
		Resize(settings.screenWidth, settings.screenHeight);
		break;

		// Press home to reset the view.
	case GLUT_KEY_HOME:
		viewZoom = 1.0f;
		settings.viewCenter.Set(0.0f, 20.0f);
		Resize(settings.screenWidth, settings.screenHeight);
		break;
	}
}

static void KeyboardUp(unsigned char key, int x, int y)
{
	B2_NOT_USED(x);
	B2_NOT_USED(y);

	if (sandBoxApp)
	{
		sandBoxApp->KeyboardUp(key);
	}
}

static void Mouse(int32 button, int32 state, int32 x, int32 y)
{
	// Use the mouse to move things around.
	if (button == GLUT_LEFT_BUTTON)
	{
		int mod = glutGetModifiers();
		b2Vec2 position = ConvertScreenToWorld(x, y);
		if (state == GLUT_DOWN)
		{
			
			b2Vec2 position = ConvertScreenToWorld(x, y);

			if (mod == GLUT_ACTIVE_SHIFT)
			{
				sandBoxApp->SetShape(!settings.bodyType, settings.bodyFixRotation, settings.boxSize, settings.ballSize, settings.bodyMass, settings.bodyRestitution, settings.bodyFriction);
			}
			else
			{
				sandBoxApp->MouseDown(position);
			}

		}
		
		if (state == GLUT_UP)
		{
			sandBoxApp->MouseUp(position);
		}
	}
	else if (button == GLUT_RIGHT_BUTTON)
	{
		int mod = glutGetModifiers();
		if (state == GLUT_DOWN)
		{	
			lastPoint = ConvertScreenToWorld(x, y);

			if (mod == GLUT_ACTIVE_SHIFT)
			{
				sandBoxApp->DeleteShape();
			}

			else
			{

				if (shapeSelection == 0)
				{
					sandBoxApp->GenerateBox(!settings.bodyType, settings.bodyFixRotation, settings.boxSize, lastPoint, settings.bodyMass, settings.bodyRestitution, settings.bodyFriction, settings.numberOfBoxes);
				}

				else if (shapeSelection == 1)
				{
					sandBoxApp->GenerateBall(!settings.bodyType, settings.bodyFixRotation, settings.ballSize, lastPoint, 0, settings.bodyMass, settings.bodyRestitution, settings.bodyFriction, settings.numberOfBalls);
				}
			}


		}

	}
}

static void MouseMotion(int32 x, int32 y)
{
	b2Vec2 p = ConvertScreenToWorld(x, y);
	sandBoxApp->MouseMove(p);

}

static void MouseWheel(int wheel, int direction, int x, int y)
{
	B2_NOT_USED(wheel);
	B2_NOT_USED(x);
	B2_NOT_USED(y);
	if (direction > 0)
	{
		viewZoom /= 1.1f;
	}
	else
	{
		viewZoom *= 1.1f;
	}
	Resize(settings.screenWidth, settings.screenHeight);
}


static void Pause(int)
{
	settings.pause = !settings.pause;
}


static void SaveScene(int)
{
	sandBoxApp->SaveMeta();
}

static void LoadScene(int)
{
	Restart(0);	
	settings.pause = true;
	sandBoxApp->LoadMeta();

}

static void ChangeGravity(int)
{
	sandBoxApp->SetGravity(settings.gravity);
}


static void Exit(int code)
{
	// TODO: freeglut is not building on OSX
#ifdef FREEGLUT
	glutLeaveMainLoop();
#endif
	exit(code);
}

static void SingleStep(int)
{
	settings.pause = 1;
	settings.singleStep = 1;
}

int main(int argc, char** argv)
{
	sandBoxApp = new SandBox2D();

	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
	glutInitWindowSize(settings.screenWidth, settings.screenHeight);
	char title[32];
	sprintf(title, "SandBox2D - Sam Latif (MA002HA) - Tools & Middleware Assignment 1 - Box2D Version %d.%d.%d", b2_version.major, b2_version.minor, b2_version.revision);
	mainWindow = glutCreateWindow(title);
	//glutSetOption (GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS);

	glutDisplayFunc(SimulationLoop);
	GLUI_Master.set_glutReshapeFunc(Resize);  
	GLUI_Master.set_glutKeyboardFunc(Keyboard);
	GLUI_Master.set_glutSpecialFunc(KeyboardSpecial);
	GLUI_Master.set_glutMouseFunc(Mouse);
#ifdef FREEGLUT
	glutMouseWheelFunc(MouseWheel);
#endif
	glutMotionFunc(MouseMotion);

	glutKeyboardUpFunc(KeyboardUp);

	glui = GLUI_Master.create_glui_subwindow( mainWindow, 
		GLUI_SUBWINDOW_RIGHT );

	glui->add_statictext("World Settings" );

	GLUI_Spinner* setGravitySpinner =
		glui->add_spinner("Gravity", GLUI_SPINNER_FLOAT, &settings.gravity.y);
	setGravitySpinner->set_float_limits(-100, 100);
	
	glui->add_button("Set", 0, ChangeGravity);
 	
	glui->add_separator();
		
	GLUI_Rollout* editShapeAttributes =
		glui->add_rollout("Edit Shapes", GLUI_PANEL_EMBOSSED );

	GLUI_Listbox* shapeList =
		glui->add_listbox_to_panel(editShapeAttributes, "Choose Shape", &shapeSelection);
	shapeList->add_item(0, "Box");
	shapeList->add_item(1, "Ball");

	GLUI_Checkbox* isStatic =
		glui->add_checkbox_to_panel(editShapeAttributes, "Set as static", &settings.bodyType, 0);

	GLUI_Checkbox* fixRotation =
		glui->add_checkbox_to_panel(editShapeAttributes, "Freeze rotation", &settings.bodyFixRotation, 0);

	GLUI_Spinner* bodyMassSpinner =
		glui->add_spinner_to_panel(editShapeAttributes, "Set Mass", GLUI_SPINNER_FLOAT, &settings.bodyMass);
	bodyMassSpinner->set_int_limits(1, 50);

	GLUI_Spinner* bodyBounceSpinner =
		glui->add_spinner_to_panel(editShapeAttributes, "Set Bounciness", GLUI_SPINNER_FLOAT, &settings.bodyRestitution);
	bodyBounceSpinner->set_float_limits(0.0f, 1.0f);

	GLUI_Spinner* bodyFrictionSpinner =
		glui->add_spinner_to_panel(editShapeAttributes, "Set Friction", GLUI_SPINNER_FLOAT, &settings.bodyFriction);
	bodyFrictionSpinner->set_float_limits(0.0f, 1.0f);
		
	GLUI_Rollout* editBoxAttributes =
		glui->add_rollout_to_panel(editShapeAttributes, "Box Attributes", 1);

	GLUI_Spinner* boxWidthSpinner =
		glui->add_spinner_to_panel(editBoxAttributes, "Width of Box", GLUI_SPINNER_FLOAT, &settings.boxSize.x);
	boxWidthSpinner->set_float_limits(1, 50);

	GLUI_Spinner* boxHeightSpinner =
		glui->add_spinner_to_panel(editBoxAttributes, "Height of Box", GLUI_SPINNER_FLOAT, &settings.boxSize.y);
	boxHeightSpinner->set_float_limits(1, 50);

	GLUI_Spinner* numOfBoxesSpinner =
		glui->add_spinner_to_panel(editBoxAttributes, "No of Boxes", GLUI_SPINNER_INT, &settings.numberOfBoxes);
	numOfBoxesSpinner->set_int_limits(1, 20);

	GLUI_Rollout* editBallAttributes =
		glui->add_rollout_to_panel(editShapeAttributes, "Ball Attributes", 1);

	GLUI_Spinner* ballSizeSpinner =
		glui->add_spinner_to_panel(editBallAttributes, "Size of Ball", GLUI_SPINNER_FLOAT, &settings.ballSize);
	ballSizeSpinner->set_float_limits(1, 50);
	
	GLUI_Spinner* numOfBallsSpinner =
		glui->add_spinner_to_panel(editBallAttributes, "No of Balls", GLUI_SPINNER_INT, &settings.numberOfBalls);
	numOfBallsSpinner->set_int_limits(1, 20);

	glui->add_button("Pause", 0, Pause);
	glui->add_button("Single Step", 0, SingleStep);
	
	glui->add_button("Restart", 0, Restart);
	glui->add_button("Save", 0, SaveScene);
	glui->add_button("Load", 0, LoadScene);

	glui->add_button("Quit", 0,(GLUI_Update_CB)Exit);
	glui->set_main_gfx_window( mainWindow );

	// Use a timer to control the frame rate.
	glutTimerFunc(16, Timer, 0);

	glutMainLoop();

	return 0;
}
